package org.traffometer.packet.location;

import java.math.BigDecimal;
import java.math.MathContext;

/**
 * Implements a representation for a physical coordinate. 
 *
 * @author Aaron A. Kagawa
 * @version $Id$
 */
public abstract class PhysicalCoordinate {
  
  /** Physical Coordinate in sexagesimal (decimal) units. */
  protected double coordinateInSexagesimal;
  private int secondPrecision = 0;
  
  /**
   * Constructs a Physical Coordinate with the specified degrees, minutes, seconds, and direction.
   * @param degrees The Physical Coordinate degrees. This value must be positive.
   * @param minutes The Physical Coordinate minutes. This value must be positive.
   * @param seconds The Physical Coordinate seconds. This value must be positive.
   * @param hemisphere The Physical Coordinate's hemisphere either 
   *   Hemisphere.North, Hemisphere.East, Hemisphere.South, or Hemisphere.West. This parameter
   *   also specifies whether the sexagesimal value will be either positive or negative.
   * @throws LocationException If an invalid parameters is passed to this constructor. 
   */
  public PhysicalCoordinate(int degrees, int minutes, double seconds, Hemisphere hemisphere) 
    throws LocationException {
    if (hemisphere != Hemisphere.North && hemisphere != Hemisphere.East && 
        hemisphere != Hemisphere.South && hemisphere != Hemisphere.West) {
      throw new LocationException("Invalid hemisphere. Must be either North, East, South, or West");
    }
    if (degrees < 0) {
      throw new LocationException("Invalid degrees. Degrees must be a positive number.");
    }
    if (minutes < 0) {
      throw new LocationException("Invalid minutes. Minutes must be a positive number.");
    }
    if (seconds < 0.0) {
      throw new LocationException("Invalid seconds. Seconds must be a positive number.");
    }
    
    this.coordinateInSexagesimal = degrees + (minutes / 60.0) + (seconds / 3600.0);
    if (hemisphere == Hemisphere.West || hemisphere == Hemisphere.South) {
      this.coordinateInSexagesimal = -this.coordinateInSexagesimal;
    }
    this.secondPrecision = new BigDecimal(seconds).precision();
    //System.out.println(this.secondPrecision);
  }
  
  /**
   * Constructs a Physical Coordinate based on its sexagesimal notation.
   * @param coordinateInSexagesimal The physical coordinate in sexagesimal units.
   */
  public PhysicalCoordinate(double coordinateInSexagesimal) {
    this.coordinateInSexagesimal = coordinateInSexagesimal;
  }
    
  /**
   * Constructs a copy of the specified Physical Coordinate object.
   * @param coordinate The physical coordinate object to be copied. 
   */
  public PhysicalCoordinate(PhysicalCoordinate coordinate) {
    this(coordinate.toSexagesimal());
  }
  
  /**
   * Returns the sexagesimal notation of this Longitude.
   * @return the sexagesimal notation of this Longitude.
   */
  public double toSexagesimal() {
    return this.coordinateInSexagesimal;
  }
  
  /**
   * Returns the degrees value of the physical coordinate from Degrees, Minutes, and Seconds. 
   * @return The degrees of the physical coordinate.
   */
  public int getDegrees() {
    return (int) Math.abs(this.coordinateInSexagesimal);
  }
  
  /**
   * Returns the minutes value of the longitude from Degrees, Minutes, and Seconds. 
   * @return The minutes of the physical coordinate.
   */
  public int getMinutes() {
    double minutes = this.coordinateInSexagesimal;
    minutes = (Math.abs(minutes) - this.getDegrees()) * 60;
    return (int) minutes;
  }

  /**
   * Returns the seconds value of the physical coordinate from Degrees, Minutes, and Seconds. 
   * @return The seconds of the physical coordinate.
   */
  public double getSeconds() {
    double seconds = this.coordinateInSexagesimal;
    seconds = (Math.abs(seconds) - this.getDegrees()) * 60;
    seconds = (seconds - (int) seconds);
    seconds *= 60;
    
    seconds = new BigDecimal(seconds).round(new MathContext(this.secondPrecision)).doubleValue();
    return seconds;
  }
    
  /**
   * TODO: temporarily 'disabling' equals method until hashCode() is implemented.
   * An object is equal to this one if it is of type Physical Coordinate and has the same 
   *   sexagesimal value.
   * @param o The object to compare.
   * @return True if the specified object equals this object. 
   */
  public boolean equals2(Object o) {
    if (o instanceof PhysicalCoordinate) {
      return coordinateInSexagesimal == ((PhysicalCoordinate) o).toSexagesimal();
    }
    else {
      return false;
    }
  }
  
}